package naga.x.net;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.Security;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.net.ssl.KeyManagerFactory;

import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.filter.ssl.BogusTrustManagerFactory;
import org.apache.mina.filter.ssl.KeyStoreFactory;
import org.apache.mina.filter.ssl.SslContextFactory;
import org.apache.mina.filter.ssl.SslFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import naga.x.App;
import naga.x.common.Updatable;
import naga.x.event.Events;
import naga.x.net.filter.ConnectionsFilter;
import naga.x.net.impl.ClientSession;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.IOUtils;
import naga.x.util.SessionUtils;

public abstract class ClientSessionService extends IoHandlerAdapter implements Service, Updatable, SessionObserver {

	protected IoAcceptor acceptor;
	protected Map<Long, Session> sessions;
	protected SessionUpdater[] updaters;

	protected static final Logger LOG = LoggerFactory.getLogger(ClientSessionService.class);

	public ClientSessionService() {
		sessions = new ConcurrentHashMap<Long, Session>(512);
	}

	@Override
	public void startup() throws ServiceException {
		SocketAcceptor acceptor = new NioSocketAcceptor();
		// read->decoder: head->...filter...->tail->handler iobuffer->message
		// write->encoder: tail->...filter...->head->handler message->iobuffer
		initSsl(acceptor);
		acceptor.getFilterChain().addFirst("conns_filter", new ConnectionsFilter());
		acceptor.getFilterChain().addLast("codec", getCodecFilter());
		acceptor.getFilterChain().addLast("pool", new ExecutorFilter());
		acceptor.setHandler(this);
		acceptor.setReuseAddress(true);
		try {
			acceptor.bind(new InetSocketAddress("0.0.0.0", getPort()));
			this.acceptor = acceptor;
		} catch (IOException e) {
			throw new ServiceException(e);
		}
		updaters = new SessionUpdater[8];
		for (int i = 0; i < updaters.length; i++) {
			updaters[i] = new SessionUpdater(i);
		}
		App.getApp().getUpdater().add(this);
	}

	private void initSsl(SocketAcceptor acceptor) throws ServiceException {
		if (!isSslEnable()) {
			return;
		}
		try {
			SslContextFactory fac = new SslContextFactory();
			String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
			if (algorithm == null) {
				algorithm = KeyManagerFactory.getDefaultAlgorithm();
			}
			KeyManagerFactory kmfac = KeyManagerFactory.getInstance(algorithm);
			KeyStoreFactory ksfac = new KeyStoreFactory();
			ksfac.setData(IOUtils.readBytes(new FileInputStream(getSslKeyStore())));
			ksfac.setPassword(getSslKeyPassword());
			fac.setKeyManagerFactory(kmfac);
			fac.setKeyManagerFactoryKeyStore(ksfac.newInstance());
			fac.setKeyManagerFactoryKeyStorePassword(getSslKeyPassword());
			fac.setTrustManagerFactory(new BogusTrustManagerFactory());
			acceptor.getFilterChain().addFirst("ssl", new SslFilter(fac.newInstance()));
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}

	@Override
	public void shutdown() throws ServiceException {
		Collection<Session> vals = sessions.values();
		for (Session sess : vals) {
			sess.close(true);
		}
		Thread t = new Thread() {
			@Override
			public void run() {
				LOG.info("[SOCKET CLOSE]TRY");
				if (acceptor != null) {
					acceptor.unbind();
					if (!acceptor.isDisposed() && !acceptor.isDisposing()) {
						acceptor.dispose();
					}
				}
				LOG.info("[SOCKET CLOSE]OK");
			}
		};
		t.setDaemon(true);
		t.start();
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		Session sess = new ClientSession(session, App.getApp().getPacketDispatcher(), this);
		session.setAttribute(SessionUtils.GAME_SESSION, sess);
		fireSessionCreated(sess);
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		Session sess = (Session) session.getAttribute(SessionUtils.GAME_SESSION);
		if (sess != null) {
			sess.close(true);
		}
	}

	@Override
	public void messageReceived(IoSession session, Object message) throws Exception {
		Session sess = (Session) session.getAttribute(SessionUtils.GAME_SESSION);
		if (sess != null && message instanceof Packet) {
			sess.received((Packet) message);
		}
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		LOG.error("[" + getId() + "]ERROR[" + cause.getMessage() + "]" + session.getId(), cause);
	}

	@Override
	public boolean update() {
		for (SessionUpdater updater : updaters) {
			updater.update();
		}
		return true;
	}

	public void broadcast(Packet packet) {
		Iterator<Session> vals = sessions.values().iterator();
		while (vals.hasNext()) {
			Session sess = vals.next();
			if (sess != null) {
				sess.send(packet);
			}
		}
	}

	@Override
	public void fireSessionCreated(Session session) {
		sessions.put(session.getId(), session);
		updaters[getUpdaterId(session)].fireSessionCreated(session);
		App.getApp().getEventManager().addEvent(Events.EVENT_SESSION_CREATED, session);
		LOG.info("[{}]SESSION CREATED[{}]{}", getId(), session.getRemoteAddress(), session.getId());
	}

	@Override
	public void fireSessionClosed(Session session) {
		sessions.remove(session.getId());
		App.getApp().getEventManager().fireEvent(Events.EVENT_SESSION_CLOSED, session);
		LOG.info("[{}]SESSION CLOSED[{}]{}", getId(), session.getRemoteAddress(), session.getId());
	}

	private int getUpdaterId(Session session) {
		return (int) (session.getId() % updaters.length);
	}

	protected boolean isSslEnable() {
		return false;
	}

	protected String getSslKeyStore() {
		return null;
	}

	protected String getSslKeyPassword() {
		return null;
	}

	protected abstract IoFilter getCodecFilter();

	protected abstract int getPort();

}
